<?php
/**
 * DB类，
 * 封装所有相关的DB操作
 */

define('CLIENT_MULTI_RESULTS', 131072); // php调用mysql存储过程返回结果集需要使用这个

/*
 * 日志开关, 记录错误 SQL 语句到日志文件中
 *
 * 如在包含此文件前 define('DB_OP_ERROR_LOG', '/tmp/db.error.log')
 * 那么在所有请求中出错的SQL语句都会被记录到此文件中
 *
 * 如果不需要记录日志, 则在包含些文件前 define('DB_OP_ERROR_LOG', false)
 *
 */
defined('DB_OP_ERROR_LOG') || define('DB_OP_ERROR_LOG', RUNTIME_PATH.'db.error.'.date("Y-m-d").'.log');

/*
 * 记录操作时间大于某个时间的 SQL 语句
 *
 * 如在包含此文件前 define('DATABASE_LOG_SQL_TIME', 0.5)
 * 那么在所有请求中如果执行时间超过 0.5 秒的 SQL 都会被记录到日志文件中
 * 此功能依赖于 DB_OP_ERROR_LOG 常量
 */
define('DB_OP_LOG_SQL_TIME', 0.5);

class DbOp {

	private $total;
	private $query_res;
	private $conn;

	// SQL 执行时间记录
    private $begin_time;

    // 是否显示调试信息 如果启用会在日志文件记录sql语句
    public $debug = false;

    // DB连接配置信息
    private $db_info = array();

    // 数据库连接列表
    private $conn_list = array();

    // 保存最近的一条SQL语句
    private $sql = '';

    // 是否已经连接数据库
    private $connected = false;

    // 当前连接ID
    private $_link_id = null;

    // 当前查询ID
    private $_query_id = null;

    // 返回或者记录影响记录数
    private $num_rows = 0;

    // 错误消息
    private $error = '';

	/**
	 * 构造器
	 */
	public function __construct() {
		//
	}

	/**
     * 取得数据库类实例
     *
     * @param array ops, $ops = array('cfg'=>DbDispatcher::dispatchTable('ips', '', 'w'), 'sql'=>DbDispatcher::loadSql('ips.get_record_by_ip'));
     * @param array data, DB操作相关的data
     *
     * @static
     * @access public
     *
     * @return DbOp
     */
    public static function getInstance() {

        $args = func_get_args();

        $params = array();
        if (count($args) > 0) {
        	$params[] = array(
        		'SERVER' => $args[0]['cfg']['SERVER'],
        		'USERNAME' => $args[0]['cfg']['USERNAME'],
        		'PASSWORD' => $args[0]['cfg']['PASSWORD'],
        	);
        }

        // 这里不能直接调用get_instance_of的自动方法调用
        $db = get_instance_of(__CLASS__, '', $params);
        call_user_func_array(array(&$db, 'factory'), $args);

        return $db;
    }

    /**
     * 加载数据库 支持配置文件或者 DSN
     *
     * @access public
     *
     * @param mixed $db_config 数据库配置信息
     *
     * @return string
     *
     * @throws AppException
     */
    public function factory($ops, $data) {

        if (!is_array($data)) $data = array();

		// 得到DB当前操作表的连接信息
		$this->db_info = $ops['cfg'];
		$this->db_info['link_key'] = to_guid_string($this->db_info['SERVER'].$this->db_info['USERNAME'].$this->db_info['PASSWORD']); // 纯粹是为了方便调试
		$ops['db_table'] = $this->db_info['DBTABLE'];

		// 得到DB当前操作的SQL语句
		if (isset($ops['no_sql']) && $ops['no_sql']) {
			$this->sql = '';
		}
		else {
			$template = new DbTemplate(); /// ::getInstance();

			$sql = $template->sql($ops, $data);

			$this->sql = $sql['do'];
		}

		// 是否开启调试
		if (C('APP_DEBUG')) $this->debug = true;
    }

    /**
     * 初始化数据库连接
     *
     * @access private
     *
     * @param boolean $master 主服务器
     *
     * @return void
     */
    private function initConnect($master=true) {
    	// 这里，判断是否采用主从模式
    	// 暂不支持

    	//if (!$this->connected)
    	$this->_link_id = $this->connect();
    }

    /**
     * 连接数据库方法
     *
     * @access public
     *
     * @throws AppException
     */
    public function connect($db_info='') {

    	// 没有传入db连接信息，直接使用默认连接信息
    	if (empty($db_info)) $db_info = $this->db_info;
    	if (!isset($db_info['link_key'])) $db_info['link_key'] = to_guid_string($db_info['SERVER'].$db_info['USERNAME'].$db_info['PASSWORD']);

    	/// 如果是命令行，使用长连接
    	if (IS_CLI) {

    		$resource = mysql_pconnect($db_info["SERVER"], $db_info["USERNAME"], $db_info["PASSWORD"]);

    		// 设置UTF8字符集
	    	mysql_query("SET NAMES 'utf8'", $resource);

	    	$this->connected = true;

	    	return $resource;
    	}

    	if (!isset($this->conn_list[$db_info['link_key']])) {

    		$this->conn_list[$db_info['link_key']] = mysql_connect($db_info["SERVER"], $db_info["USERNAME"], $db_info["PASSWORD"], true, CLIENT_MULTI_RESULTS);

    		if (!$this->conn_list[$db_info['link_key']]) {
	    	    $this->conn_list[$db_info['link_key']] = mysql_connect($db_info["SERVER"], $db_info["USERNAME"], $db_info["PASSWORD"], true, CLIENT_MULTI_RESULTS);
	    	    if (!$this->conn_list[$db_info['link_key']]) {
                    throw_exception(mysql_error());
	    	    }
	    	}

	    	// 设置UTF8字符集
	    	mysql_query("SET NAMES 'utf8'", $this->conn_list[$db_info['link_key']]);

	    	// 标记连接成功
	    	$this->connected = true;

	    	// 注销数据库配置信息
	    	// 待定
	    	//unset($this->db_info);
    	}

    	return $this->conn_list[$db_info['link_key']];
    }

	/**
     *
     * 释放查询结果
     *
     * @access public
     */
    public function free() {
        @mysql_free_result($this->_query_id);
        $this->_query_id = 0;
    }

    /**
     * query, include:R
     * 执行查询 返回数据集
     *
     * @param string $str, 要查询的sql，可以为空，采用默认(从db_info和input_ary里获得)
     *
     * @return mixed
     *
     * @throws AppException
     */
    public function query($str='') {

    	if ($str) $this->sql = $str;
    	C('_sql', $this->sql);

    	// 初始化数据库连接
		$this->initConnect(true);
		if (!$this->_link_id) return false;

		// 释放前次的查询结果
		if ($this->_query_id) { $this->free(); }

		// R统计次数+1
		$this->Q(1);

		// 开始执行R
		if (DB_OP_LOG_SQL_TIME > 0) {
            $tmp_time = microtime(true);
		    $this->_query_id = mysql_query($this->sql, $this->_link_id);
		    $tmp_time = microtime(true) - $tmp_time;
		    if ($tmp_time > DB_OP_LOG_SQL_TIME) {
				$this->logError(sprintf("[Q] [%s] %s\n[R] %.5f sec consumed", date("m/d H:i:s"), $this->sql, $tmp_time), 'info');
            }
		}
		else {
		    $this->_query_id = mysql_query($this->sql, $this->_link_id);
		}
		$this->debug();

		if (false === $this->_query_id) {
		    $this->logError(sprintf("[Q] [%s] %s \n[E] %s\n", date("m/d H:i:s"), $this->sql, mysql_error($this->_link_id)), 'error');
			$this->error();
            return false;
		}
		else {
			if ( is_resource($this->_query_id) ) {
				$this->num_rows = mysql_num_rows($this->_query_id);

            	return $this->mysqlFetchAll();
			}
		}
    }

	/**
	 * execute, include:C/U/D
	 *
	 * @param string $str, 要执行的sql，可以为空，采用默认(从db_info和input_ary里获得)
     *
     * @return integer
     *
     * @throws AppException
	 */
	public function execute($str='') {

		if ($str) $this->sql = $str;
		C('_sql', $this->sql);

		// 初始化数据库连接
		$this->initConnect(true);
		if (!$this->_link_id) return false;

		// 释放前次的查询结果
		if ($this->_query_id) { $this->free(); }

		// CUD统计次数+1
		$this->W(1);

		// 开始执行CUD
		if (DB_OP_LOG_SQL_TIME > 0) {
            $tmp_time = microtime(true);
		    $result = mysql_query($this->sql, $this->_link_id);
		    $tmp_time = microtime(true) - $tmp_time;
		    if ($tmp_time > DB_OP_LOG_SQL_TIME) {
				$this->logError(sprintf("[Q] [%s] %s\n[R] %.5f sec consumed", date("m/d H:i:s"), $this->sql, $tmp_time), 'info');
            }
		} else {
            $result = mysql_query($this->sql, $this->_link_id);
		}
		$this->debug();

		if (false === $result) {
		    $this->logError(sprintf("[Q] [%s] %s \n[E] %s\n", date("m/d H:i:s"), $this->sql, mysql_error($this->_link_id)), 'error');
			$this->error();
            return false;
		}
		else {
			$this->num_rows = mysql_affected_rows($this->_link_id);
            return $this->num_rows;
		}
	}

	/**
	 * 返回这个查询数据集
	 */
	public function getAssoc($str='') {

		return $this->query($str);
	}

	/**
	 * 返回查询数据集的第一行
	 */
	public function getRow($str='') {

		$rows = $this->query($str);

		if (is_array($rows)) {
			if (count($rows) > 0) {
				return array_shift($rows);
			}
			else {
				return array();
			}
		}

		return false;
	}

	/**
	 * @deprecated 不推荐使用
	 *
	 * 返回查询总数
	 */
	public function getOne($str='') {

		$rows = $this->query($str);

		if (is_array($rows) && (count($rows) > 0))
			return array_shift($rows[0]);

		return false;

	}

	public function getInsertId() {

		return mysql_insert_id($this->_link_id);
	}

	/**
	 * 设置sql语句
	 *
	 * @param string $sql
	 */
	public function setSql($str='') {
		if (!empty($str)) $this->sql = $str;
	}

	/**
	 * 获得所有的查询数据
     *
     * @access private
     *
     * @return array
	 */
	private function mysqlFetchAll() {

		//返回数据集
        $ret = array();
        if($this->num_rows >0) {
            while($row = mysql_fetch_assoc($this->_query_id)){
                $ret[]   =   $row;
            }
            mysql_data_seek($this->_query_id, 0);
        }
        return $ret;
	}

	/**
     * 数据库调试 记录当前SQL的信息（sql语句、runtime）
     *
     * @access private
     */
    private function debug() {

        // 记录操作结束时间
        if ( $this->debug )    {
            $runtime    =   number_format(microtime(true) - $this->begin_time, 6);
            Log::record(" RunTime:".$runtime."s SQL = ".$this->sql,Log::SQL);
        }
    }

	/**
     * 查询次数更新或者查询
     *
     * @access public
     *
     * @param mixed $times
     *
     * @return void
     */
    public function Q($times='') {
        static $_times = 0;
        if(empty($times)) {
            return $_times;
        }else{
            $_times++;
            // 记录开始执行时间
            $this->begin_time = microtime(true);
        }
    }

    /**
     * 写入次数更新或者查询
     *
     * @access public
     *
     * @param mixed $times
     *
     * @return void
     */
    public function W($times='') {
        static $_times = 0;
        if(empty($times)) {
            return $_times;
        }else{
            $_times++;
            // 记录开始执行时间
            $this->begin_time = microtime(true);
        }
    }

    /**
     * 关闭数据库
     *
     * @access public
     *
     * @throws AppException
     */
    public function close() {
        if (!empty($this->_query_id))
            @mysql_free_result($this->_query_id);
        if ($this->_link_id && !mysql_close($this->_link_id)){
            throw_exception($this->error());
        }
        $this->_linkID = 0;
    }

    /**
     * 数据库错误信息
     * 并显示当前的SQL语句
     *
     * @access public
     *
     * @return string
     */
    public function error() {
        $this->error = mysql_error($this->_link_id);
        if($this->debug && '' != $this->sql){
            $this->error .= "\n [ SQL语句 ] : ".$this->sql;
        }

        if ($this->debug || in_array('RUN_MOD', array('local', 'devel', 'test'))) trigger_error($this->error, E_USER_ERROR);

        return $this->error;
    }

    /**
     * SQL指令安全过滤
     *
     * @access public
     *
     * @param string $str  SQL字符串
     *
     * @return string
     */
    public function escapeString($str) {
        return mysql_escape_string($str);
    }

   /**
     * 析构方法
     *
     * @access public
     */
    public function __destruct() {
        // 关闭连接
        $this->close();
    }

    /**
     * 获取最近一次查询的sql语句
     *
     * @access public
     *
     * @return string
     */
    public function getLastSql() {
        return $this->sql;
    }

    /**
     * 获取最近的错误信息
     *
     * @access public
     *
     * @return string
     */
    public function getError() {
        return $this->error;
    }

    /**
	 * @deprecated
	 */
    public function jsonFormat($rows) {
	    $buf="[";

        if (count($rows) > 0) {
    	    foreach($rows as $row) {
	            $buf .= "[";

    	        foreach($row as $rowval) {
	                $rowval = str_replace('"',"&quot;",$rowval);
        	        $buf .= "\"" . $rowval . "\",";
    	        }

           		$buf{strlen($buf)-1} = "]";
            	$buf .= ",";
    	    }
        }

	    if(count($rows) > 0) {
	        $buf{strlen($buf)-1} = "]";
	    }
	    else {
	        $buf .= "]";
	    }

	    return $buf;
	}

	/**
	 * @deprecated
	 */
	public function getJson($print_sql) {

		if ($this->query_res > 0) {
			$rows = $this->mysql_fetch_all($this->query_res);
			$this->json = $this->jsonFormat($rows);
            if (!$this->total) {
                $this->total = count($rows);
            }
            $return = "{code:1, msg:'succ', total:" . $this->total . ", data:" . $this->json . '}';
            return $return;
		}
		else {
			return "{code:-1,msg:'" . mysql_error() . ".'}";
		}

	}

	/**
	 * @deprecated
	 */
	public function getJsonData(){
		$rows = $this->query($this->sql);
		return $this->jsonFormat($rows);
	}

	/*
	 * 记录错误日志
	 *
	 * @param string $message	日志信息
	 * @param string $level		错误级别
	 * @return void
	 *
	 */
	protected function logError($message, $level = LOG_PRI_ERROR) {
		if ($message) {
			error_log($message, 3, DB_OP_ERROR_LOG);
		}
	}
}
?>